import util from 'util'
/**
 * @description: 严格类型判断
 * @param {any} typeObject
 * @return {String} Object Array
 */
export function toClass(objectProp) {
  if (!arguments.length) {
    throw Error('Arg: ObjectProp must be required')
  }
  return Object.prototype.toString
    .call(objectProp)
    .split(' ')[1]
    .replace(']', '')
}

export const toType = toClass

/**
 * @description: 严格类型判断
 * @param {any} typeObject
 * @param {String} type
 * @return {Boolean}
 */
export function isType(objectProp, typeProp) {
  if (!arguments.length) {
    throw Error('Arg: objectProp must be required')
  }
  if (!typeProp) {
    throw Error('Arg: types must be required')
  }
  const objectPropType = toClass(objectProp)
  return objectPropType === typeProp
}

/**
 * @description: 严格类型判断
 * @param {any} typeObject
 * @param {String} type
 * @return {Boolean}
 */
export function isTypes(objectProp, typesProp) {
  if (!arguments.length) {
    throw Error('Arg: objectProp must be required')
  }
  if (!typesProp) {
    throw Error('Arg: types must be required')
  }
  if (typeof typesProp === 'string') {
    return isType(objectProp, typesProp)
  } else if (Array.isArray(typesProp)) {
    const objectPropType = toClass(objectProp)
    return typesProp.includes(objectPropType)
  } else {
    throw Error('Arg: objectProp type must be Array or String')
  }
}

/**
 * @description: 查找key是否在对象上（不包括对象原型）
 * @param {Object} obj
 * @param {String} key
 * @param {Boolean} typeVf 是否对obj参数进行强类型验证
 * @return {Boolean}
 */
export function hasOwn(obj, key, typeVf = true) {
  if (obj) {
    if ((typeVf && isType(obj, 'Object')) || !typeVf) {
      return Object.hasOwnProperty.call(obj, key)
    }
  } else {
    throw Error('the obj params is required')
  }
}

/**
 * @description: 字符串去除空格
 * @param {String} str
 * @param {String} insetAuto 是否去除字符串内部空白符
 * @return {String}
 */
export function trimAll(str, insetAuto) {
  if (str) {
    if (!insetAuto) {
      return str.trim()
    } else {
      return str.replace(/\s/gm, '')
    }
  } else {
    return str
  }
}

/**
 * This is just a simple version of deep copy
 * Has a lot of edge cases bug
 * If you want to use a perfect deep copy, use lodash's _.cloneDeep
 * @param {Object} source
 * @returns {Object}
 */
export function deepClone(source) {
  if (!source && typeof source !== 'object') {
    throw new Error('error arguments', 'deepClone')
  }
  const targetObj = source.constructor === Array ? [] : {}
  Object.keys(source).forEach((keys) => {
    if (source[keys] && typeof source[keys] === 'object') {
      targetObj[keys] = deepClone(source[keys])
    } else {
      targetObj[keys] = source[keys]
    }
  })
  return targetObj
}

/**
 * @description: 深拷贝
 * @param {Object} jsonObject
 * @return {Object}
 */

export function extend(jsonObject) {
  if (!arguments.length) {
    throw Error('Arg: jsonObject must be required')
  }
  return JSON.parse(JSON.stringify(jsonObject))
}

/**
 * @description: 混入
 * @param {Object} to
 * @param {Object} from
 * @return {Object}
 */
export function mixin(to, from) {
  if (!from) { return to }
  let key, toVal, fromVal
  const keys = Object.keys(from)
  for (let i = 0, len = keys.length; i < len; i++) {
    key = keys[i]
    // in case the object is already observed...
    if (key === '__ob__') { continue }
    toVal = to[key] // child
    fromVal = from[key] // parent
    const toValType = toClass(toVal)
    const fromValType = toClass(fromVal)
    if (!hasOwn(to, key)) {
      to[key] = fromVal // 若to没有此key，添加它
    } else if (
      (toValType === 'Object' || toValType === 'Array') &&
      toVal !== fromVal &&
      toValType === fromValType
    ) {
      // 若to有此key，且不等
      // 若to有此key，值非对象，否则进行深度合并
      mixin(toVal, fromVal)
    }
  }
  return to
}

/**
 * @description: 合并
 * @param {Object} to
 * @param {Object} from
 * @return {Object}
 */
export function merge(to, from) {
  if (!from) { return to }
  let key, toVal, fromVal
  const keys = Object.keys(from)
  for (let i = 0, len = keys.length; i < len; i++) {
    key = keys[i]
    // in case the object is already observed...
    if (key === '__ob__') { continue }
    toVal = to[key] // child
    fromVal = from[key] // parent
    const toValType = toClass(toVal)
    const fromValType = toClass(fromVal)
    if (
      (toValType === 'Object' || toValType === 'Array') &&
      toValType === fromValType &&
      toVal !== fromVal
    ) {
      // 若to有此key，且不等
      // 若to有此key，值非对象，否则进行深度合并
      merge(toVal, fromVal)
    } else {
      to[key] = fromVal
    }
  }
  return to
}

/*
  格式化数字
  val: Number | String 格式化字符
  config: Object->{ 格式化配置
    length: Number 数字格式化长度
    fill: String 数字格式化填充字符
  }
  return String
  demo:
    const val = 1
    const formatVal = formatNumberOfCode(val) // output: "01"
*/
export function formatNumberOfCode(val, config) {
  config = Object.assign({
    // 长度
    length: 2,
    // 填充字符
    fill: '0'
  }, config)

  if (val) {
    const valStr = val.toString()
    if (val.length >= config.length) {
      return valStr.slice(-1)
    } else {
      return Array.from({ length: config.length - valStr.length }).map(_ => config.fill).join('') + valStr
    }
  }
}

/*
  创建一个枚举对象
  prop:
    eumnConfigProp // Object
  return: Object
*/
export function creatEumn(eumnConfigProp) {
  if (!eumnConfigProp) {
    throw Error('Arg: eumnConfigProp must be required')
  }
  if (!isType(eumnConfigProp, 'Object')) {
    throw Error('Arg: eumnConfigProp type must be Object')
  }
  const eumnObj = {}
  for (const key in eumnConfigProp) {
    const val = eumnConfigProp[ key ]
    // 如果枚举值不是一个有效字符串或者数字
    if (!isTypes(val, ['Number', 'String'])) {
      throw Error(`eumnConfigProp[${key}] type must be a vaild String or Number`)
    }
    eumnObj[ key ] = val
    eumnObj[ val ] = key
  }
  return eumnObj
}

// Colors
export function hexToRgba(hexValue, opacity = 1) {
  const rgx = /^#?([a-f\d])([a-f\d])([a-f\d])$/i
  const hex = hexValue.replace(rgx, (m, r, g, b) => r + r + g + g + b + b)
  const rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
  const r = parseInt(rgb[1], 16)
  const g = parseInt(rgb[2], 16)
  const b = parseInt(rgb[3], 16)
  return `rgba(${r},${g},${b},${opacity})`
}
/*
 *16进制颜色转为RGB格式
 *直接定义在String.prototype属性上
 */
export function hslToRgba(hslValue, opacity = 1) {
  const hsl = /hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(hslValue)
  const h = parseInt(hsl[1]) / 360
  const s = parseInt(hsl[2]) / 100
  const l = parseInt(hsl[3]) / 100
  function hue2rgb(p, q, t) {
    if (t < 0) t += 1
    if (t > 1) t -= 1
    if (t < 1 / 6) return p + (q - p) * 6 * t
    if (t < 1 / 2) return q
    if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6
    return p
  }
  let r, g, b
  if (s === 0) {
    r = g = b = l
  } else {
    const q = l < 0.5 ? l * (1 + s) : l + s - l * s
    const p = 2 * l - q
    r = hue2rgb(p, q, h + 1 / 3)
    g = hue2rgb(p, q, h)
    b = hue2rgb(p, q, h - 1 / 3)
  }
  return `rgba(${r * 255},${g * 255},${b * 255},${opacity})`
}
/*
  设置颜色透明度
  prop:
    colorStr // String 有效的颜色字符串 不包括 关键字字符串 如 white这种
    opacity // Number 颜色透明度
*/
export function setColorOpacity(colorStr, opacity = 1) {
  if (!colorStr || typeof colorStr !== 'string') {
    throw Error('Arg: colorStr must be a vaild String')
  }
  if (typeof opacity !== 'number') {
    throw Error('Arg: opacity type must be Number')
  }
  // 16进制正则
  const hexReg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
  // rgba正则
  const rgbReg = /^rgb\(\s*\d{1,3}\s*\,\s*\d{1,3}\s*\,\s*\d{1,3}\s*\)$/
  // rgba正则
  const rgbaReg =
    /^rgba\((\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*)((?:,\s*[0-9.]*\s*)?)\)$/
  // hsl正则
  const hslReg = /^hsl/
  let rgbaColorStr = ''
  if (hexReg.test(colorStr)) {
    rgbaColorStr = hexToRgba(colorStr, opacity)
  } else if (rgbReg.test(colorStr)) {
    rgbaColorStr = colorStr.replace(/\)/, `,${opacity})`).replace('rgb', 'rgba')
  } else if (rgbaReg.test(colorStr)) {
    rgbaColorStr = colorStr.replace(/\,\d\)/, `,${opacity})`)
  } else if (hslReg.test(colorStr)) {
    rgbaColorStr = hslToRgba(colorStr, opacity)
  } else {
    throw Error(`The colorStr ${colorStr} is not a correct color`)
  }
  const rgbaColorFormatStr = rgbaColorStr.replace(/\s+/, '')
  return rgbaColorFormatStr
}

// 只消费最后一次“调用”的结果，而不是最后一个返回的结果
// 1.同一个函数只消费最后一次调用的结果
// 2.不同函数各自消费自己最后一次的调用结果
// 3.每条请求并没有取消，只是没有消费它的结果
export const doJobLastInvoked = (function() {
  const token = new WeakMap()
  return function addJob(fn) {
    const ticket = Symbol()
    token.set(fn, ticket)
    const p = (...args) => new Promise((resolve, reject) => {
      fn(...args).then(res => {
        const currentToken = token.get(fn)
        if (currentToken && currentToken === ticket) {
          resolve(res)
          token.delete(fn)
        } else {
          reject('callback is out of date.')
        }
      })
    })
    return p
  }
})()

// 检测传入的Promise是否处于Pending状态
export const isPending = async(p) => {
  const empty = {}
  try {
    const resp = await Promise.race([p, empty])
    return resp === empty
  } catch (e) {
    return false
  }
}

const PROMISE_STATE = {
  PENDING: 'pending',
  FULFILLED: 'fulfilled',
  REJECTED: 'rejected'
}
/*
  检测 promise 状态
  prop:
    promise // Promise
  return Boolean
*/
export function decidePromiseState(promiseProp) {
  if (!arguments.length) {
    throw Error('Arg: promiseProp must be required')
  }
  if (promiseProp instanceof Promise === false) {
    throw Error('Arg: promiseProp must be a vaild Promise Object')
  }
  const inspectStr = util.inspect(promiseProp)
  if (inspectStr.includes(PROMISE_STATE.PENDING)) {
    return PROMISE_STATE.PENDING
  } else if (inspectStr.includes(PROMISE_STATE.REJECTED)) {
    return PROMISE_STATE.REJECTED
  } else {
    return PROMISE_STATE.FULFILLED
  }
}

/**
 * @description: 高频调用同一个js函数操作dom，造成回流重绘的性能瓶颈时使用
 * @param {() => Boolean} reflowFn
 */
export function useRAF(reflowFn) {
  const step = () => {
    const endPoint = reflowFn()
    if (!endPoint) {
      requestAnimationFrame(step)
    }
  }
  requestAnimationFrame(step)
}

// 转换横线字符为驼峰字符
export function hyphenToCameCase(hyphenStr) {
  if (typeof hyphenStr === 'string') {
    return hyphenStr.replace(/\-[a-z]/g, (match) => {
      if (match) {
        return match.slice(1).toUpperCase()
      }
    })
  } else {
    throw Error('hyphenStr type must be String')
  }
}

// 从一个对象中排除一些不想被引用的key
export const omit = (obj, keys) => {
  const res = {}
  Object.keys(obj)
    .filter(key => !keys.includes(key))
    .forEach(key => {
      res[key] = obj[key]
    })
  return res
}

